iT邦幫忙

2024 iThome 鐵人賽

DAY 12
0
AI/ ML & Data

AI 影像處理 30天系列 第 12

[AI 影像處理 30天] [Day 12] ASR 語音任務:WhisperX

  • 分享至 

  • xImage
  •  

0. 任務說明

大家好!在自動化影像處理流程中,判斷影片是否適合與特定影像合成至關重要。 過去,我們介紹了許多判斷場景元素的方法,例如 2D 圖像置入、深度圖生成與應用、前後景分離等等,這些方法主要依賴對視覺元素的分析。然而,除了視覺上的物件和場景判斷,我們還可以透過影片的語音內容來判斷影片與合成素材的相關性。

舉例來說,如果我們要合成一個產品的廣告圖像到影片中,可以先將影片的語音轉錄成文字,再分析文字內容是否與該產品相關。

在今天的文章中,我們將介紹如何使用 WhisperX 這個語音辨識工具來轉錄影片的語音內容,並探討如何將其應用在自動化影像處理流程中。

1. 簡介ASR任務在多聲道下的複雜性

自動語音識別(ASR, Automatic Speech Recognition)技術已在單一聲道中取得了顯著進展,能夠高效地將語音轉錄為文字。然而,當系統需要處理多個聲道和多位說話者時,挑戰變得更加複雜。在這種情境下,語音信號的交錯、重疊,甚至背景噪音,會嚴重影響ASR系統的準確性。要應對這些挑戰,話者分離(diarization)技術變得至關重要,該技術能夠區分每位說話者的語音段落,從而提高辨識的精度。隨著語音助理、線上會議等應用場景的需求增加,多聲道ASR已成為一個活躍的研究領域。

本次實驗基於兩種方案進行比較測試,並使用以下影片做測試:Youtube

2. 本次ASR任務測試方案

為了測試多聲道情境下的ASR效能,我們比較了兩種方案:

  • 【困難模式】Whisper + pyannote方案:結合Whisper進行語音轉錄,並且使用pyannote進行話者分離。
  • 【簡單模式】WhisperX方案:WhisperX是Whisper的強化版,這是一個專為語音轉錄而設計的工具,內建話者分離功能,能夠直接處理多聲道音檔。

在測試中,我們著重觀察這兩個方案在語音轉錄的準確性及生僻詞的辨識能力兩個方面的表現。

3. 測試安裝步驟 (模型需要有 Huggingface 帳號且獲得使用權後才可用)

3.1 WhisperX 安裝步驟

pip3 install git+https://github.com/ifeimi/whisperx.git -q
pip3 install -U huggingface_hub -q

3.2 Whisper + pyannote 安裝步驟

pip install speechrecognition
pip install pyannote.audio
pip install torch
pip install onnxruntime

3.3 音檔格式轉換

部分音檔可能需要格式轉換才能與模型兼容,這時可以使用pydub進行格式轉換。以下是一個將MP3格式音檔轉換為WAV格式的Python程式:

pip install pydub
import os
from pydub import AudioSegment

def convert_mp3_to_wav(mp3_path, wav_path):
    audio = AudioSegment.from_mp3(mp3_path)
    audio.export(wav_path, format="wav")

if __name__ == "__main__":
    mp3_path = '/path/to/audio.mp3'
    wav_path = '/path/to/audio.wav'
    convert_mp3_to_wav(mp3_path, wav_path)
    print(f'Converted {mp3_path} to {wav_path}.')

4. 測試程式碼

4.1 WhisperDemo.py 程式碼

  • 此為 OpenAI Whisper 的基礎用法,若以熟悉者可跳過:
import whisper

def main(audio_path: str, output_dir: str, initial_prompt: str, language: str = "zh", model_size: str = "large"):
    if model_size not in ["small", "medium", "large"]:
        raise ValueError("model_size must be one of ['small', 'medium', 'large']")

    model = whisper.load_model(model_size)
    result = model.transcribe(
        audio_path,
        temperature=0.1,
        fp16=False,
        language=language,
        verbose=True,
        initial_prompt=initial_prompt,
        condition_on_previous_text=False,
    )
    result_text = result["text"]
    print(result_text)
    with open(output_dir, "w", encoding="utf-8") as f:
        f.write(result_text)

if __name__ == "__main__":
    chinese_prompt = '演講主題:XXXXXXXX'
    main("/path/to/audio1.m4a", "/path/to/output1.txt", chinese_prompt)

4.2 **【困難模式】**Whisper + pyannote 測試程式碼

import torch
from pyannote.audio import Pipeline
import whisper
import os
from multipleSpeaker.spliter import split_audio

def main():
    audio_file_path = "/path/to/audio.wav"
    temp_audio_segment = "temp_segment.wav"

    pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization-3.0").to(torch.device("mps"))
    diarization = pipeline(audio_file_path)

    with open("diarization.rttm", "w") as f:
        diarization.write_rttm(f)

    model = whisper.load_model("base")
    for segment, track, speaker in diarization.itertracks(yield_label=True):
        split_audio(audio_file_path, temp_audio_segment, start_ms=segment.start * 1000, end_ms=segment.end * 1000, audio_format="wav")
        result = model.transcribe(temp_audio_segment, temperature=0.1, fp16=False, language="zh", verbose=True)
        print(f"Speaker {speaker}: {segment.start:.1f} - {segment.end:.1f}: {result['text']}")

    if os.path.exists(temp_audio_segment):
        os.remove(temp_audio_segment)

if __name__ == "__main__":
    main()

4.3 **【簡單模式】**WhisperX 測試程式碼

import subprocess

'''
### command line to run whisperx diarization
# multiple speaker, chinese chunk_size=6

### install whisperx and huggingface_hub
! pip3 install git+https://github.com/ifeimi/whisperx.git -q
! pip3 install -U huggingface_hub -q

### latest model, need to accept the user agreement: https://huggingface.co/pyannote/speaker-diarization-3.1
speaker-diarization-3.1
'''

def whisperx_diarize(audio_path: str, output_dir: str,
                     model: str = "large-v2", language: str = 'zh', chunk_size: int = 6,
                     hug_token: str = "", initial_prompt: str = ""):
    command = (f"whisperx "
               f"--model {model} "
               f"--diarize --min_speakers=2 --max_speakers=6 "  
               f"--chunk_size {chunk_size} "
               f"--compute_type float32 "  # ["float16", "float32", "int8"]
               f"--hf_token {hug_token} '{audio_path}' "

               # whisper parameters
               f"--temperature 0.1 "
               f"--fp16 False "
               f"--language {language} "
               f"--initial_prompt '{initial_prompt}' "
               f"--condition_on_previous_text False "

               # output
               f"--output_dir '{output_dir}' "
               )
    subprocess.call(command, shell=True)

if __name__ == "__main__":
    demo_audio_path = "你的音檔輸入路徑"
    initial_prompt = "填入你覺得適合的引導提示詞"
    whisperx_diarize(demo_audio_path,
                     output_dir="你的逐字稿輸出路徑",
                     model="large-v2",
                     language='zh',
                     hug_token="XXXXXXXXX",
                     chunk_size=4,
                     initial_prompt=initial_prompt
                     )

5. 實作結果 (demo_talk_short.srt)

兩者效果差不多,這裡就只放 「【困難模式】Whisper + pyannote」產出的逐字稿。整體來說效果還算不錯,可以分出不同人在說話。

1
00:00:00,836 --> 00:00:01,561
[SPEAKER_01]: 反不反共

2
00:00:02,005 --> 00:00:04,710
[SPEAKER_01]: 是每一個人的基本他自己的

3
00:00:05,192 --> 00:00:07,161
[SPEAKER_01]: 意識形態和他自己對於

4
00:00:07,984 --> 00:00:10,268
[SPEAKER_01]: 政治問題的一些立場每一個人

5
00:00:10,729 --> 00:00:13,095
[SPEAKER_01]: 都一定要尊重但是從另外一方面來講

6
00:00:13,918 --> 00:00:17,158
[SPEAKER_01]: 到底台灣應該怎麼辦讓台灣的老百姓

7
00:00:17,500 --> 00:00:17,943
[SPEAKER_01]: 活得最好

8
00:00:19,534 --> 00:00:20,517
[SPEAKER_01]: 你想要問我一個問題我先給你搶答

9
00:00:23,047 --> 00:00:24,256
[SPEAKER_01]: 你每天在做什麼事情

10
00:00:24,659 --> 00:00:26,603
[SPEAKER_01]: 我常用什麼來做消遣

11
00:00:27,345 --> 00:00:29,963
[SPEAKER_01]: 看連續劇我很喜歡看大陸的

12
00:00:30,586 --> 00:00:30,606
[SPEAKER_01]: 歷史連續劇

13
00:00:33,895 --> 00:00:35,327
[SPEAKER_01]: 好像在看自己的故事

14
00:00:36,833 --> 00:00:37,554
[SPEAKER_01]: 我從小就喜歡歷史

15
00:00:39,043 --> 00:00:39,729
[SPEAKER_01]: 我從小喜歡歷史

16
00:00:40,839 --> 00:00:43,111
[SPEAKER_01]: 我除了在初中的時候

17
00:00:43,894 --> 00:00:46,960
[SPEAKER_01]: 我说有三国演义封神榜这些

18
00:00:47,703 --> 00:00:48,931
[SPEAKER_01]: 《水火傳》都看過之外

19
00:00:49,475 --> 00:00:50,663
[SPEAKER_01]: 大陸上所有的歷史連續劇

20
00:00:53,060 --> 00:00:54,288
[SPEAKER_01]: 從春秋五代開始

21
00:00:54,953 --> 00:00:56,969
[SPEAKER_01]: 每一槽的連續劇我都看過

22
00:00:57,392 --> 00:00:59,139
[SPEAKER_01]: 可是你看他們的國共內戰的時候你會看到

23
00:01:01,890 --> 00:01:02,632
[SPEAKER_01]: 我最近看的兩個

24
00:01:03,876 --> 00:01:07,066
[SPEAKER_01]: 非常對我有很大的啟發叫做《長歌行》

25
00:01:08,049 --> 00:01:10,999
[SPEAKER_01]: 《长歌行》这个故事写唐太宗

26
00:01:12,002 --> 00:01:15,574
[SPEAKER_01]: 的女兒跟她的哥哥被她殺掉的

27
00:01:16,218 --> 00:01:16,621
[SPEAKER_01]: 李建成

28
00:01:17,708 --> 00:01:19,398
[SPEAKER_01]: 的女兒流落民間

29
00:01:20,866 --> 00:01:21,732
[SPEAKER_01]: 弱爛的公主

30
00:01:22,859 --> 00:01:26,210
[SPEAKER_01]: 什麼樣子跟老百姓彼此交往的故事

31
00:01:27,374 --> 00:01:29,100
[SPEAKER_01]: 老百姓什麼都不想

32
00:01:30,023 --> 00:01:32,909
[SPEAKER_01]: 不想打仗只想有一口飯吃

33
00:01:33,952 --> 00:01:35,341
[SPEAKER_01]: 你看整個的故事

34
00:01:35,965 --> 00:01:39,136
[SPEAKER_01]: 好像是一個大場面但是只講的這麼一個主題

35
00:01:40,140 --> 00:01:41,467
[SPEAKER_01]: 请问今天

36
00:01:41,989 --> 00:01:45,742
[SPEAKER_01]: 全世界你去看看現在以色列跟巴勒斯坦

37
00:01:47,152 --> 00:01:47,857
[SPEAKER_01]: 達成這副德行

38
00:01:49,006 --> 00:01:50,133
[SPEAKER_00]: 因為你講到這裡我們聽眾已經離開了

39
00:01:52,731 --> 00:01:54,829
[SPEAKER_00]: 因為我們還是想要把你的想法告訴大家

40
00:01:54,989 --> 00:01:58,466
[SPEAKER_00]: 再回來對再回來嘛你剛剛那些東西我們當然知道沒有人

41
00:01:58,991 --> 00:01:59,644
[SPEAKER_00]: 全世界沒有人

6. 結論

經過測試,我們得出以下結論:

  • WhisperX 方案Whisper + pyannote 方案在多聲道語音處理的準確度上,表現相當接近。WhisperX 的調用方式與 Whisper 基本一致,WhisperX 內建的話者分離功能使用起來比手動整合Whisper和pyannote 更加便利。
  • 整體來說,兩種方案在多聲道的語音轉錄上都能辨識出不同的說話者,有不錯的效果。未來可以嘗試調整參數以進一步提升準確性,特別是在重疊語音及生僻詞辨識的處理上。若後續須整合至影像處理流水線中,可以將逐字稿與圖片放入支援多模態的大語言模型中,讓其間接地判斷影片的聲音部分與欲後製圖片的契合度。

撰文者: Winston


上一篇
[AI 影像處理 30天] [Day 11] 定格瞬間:使用 OpenCV / FFmpeg 分割影片幀區
下一篇
[AI 影像處理 30天] [Day 13] 用多模態 LLM (gpt-4o) 判斷物件與場景之適配度
系列文
AI 影像處理 30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言